﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CodeGenerator
{
    class DwarfParser : BaseDwarfParser
    {
        public DwarfParser(DwarfScanner scanner)
        {
            this.scanner = scanner;
        }
     
        public void Parse()
        {
            Reset();
            ParseProgram();
        }

        private void ParseProgram()
        {
            AstMainClass mainClass = ParseMainClass();
            tree.SetRootNode(mainClass);            
        }

        private AstMainClass ParseMainClass()
        {
            ParseToken(TokenType.CLASS);
            AstId className = ParseId();
            ParseToken(TokenType.LEFT_BRACE);
            List<AstVarDeclaration> declarations = ParseFieldDeclarations(); 
            AstEntryPointMainFunctionDecl mainMethodDeclaration = ParseMainMethodDeclaration();
            ParseToken(TokenType.RIGHT_BRACE);
            return new AstMainClass(className, declarations, mainMethodDeclaration);
        }

        private List<AstVarDeclaration> ParseFieldDeclarations()
        {
            List<AstVarDeclaration> list = new List<AstVarDeclaration>();

            while (GetForwardToken().Type != TokenType.PUBLIC) // TODO: replace with AST_ACCESS
            {
                list.Add(ParseArgumentDecl());
                ParseToken(TokenType.SEMICOLON);
            }

            return list;
        }
        
        private AstId ParseId()
        {
            ParseToken(TokenType.ID);
            return new AstId(currentToken.Attribute);
        }

        private AstEntryPointMainFunctionDecl ParseMainMethodDeclaration()
        {
            ParseToken(TokenType.PUBLIC);
            AstAccessModifier accessModifier = new AstAccessModifier();
            ParseToken(TokenType.STATIC);
            AstStaticModifier staticModifier = new AstStaticModifier();
            AstId type = ParseId();
            Check(currentToken.Attribute == "void"); // TODO: remove const
            ParseToken(TokenType.MAIN);
            AstId functionName = new AstId("main");  // TODO: remove const
            AstFunctionArgumentsList argumentsList = ParseFunctionArgumentList();
            AstStatements stmts = ParseStatements();

            return new AstEntryPointMainFunctionDecl(
                    accessModifier, 
                    staticModifier, 
                    type, 
                    functionName, 
                    argumentsList,
                    stmts
                );
        }

        private AstFunctionArgumentsList ParseFunctionArgumentList()
        {
            ParseToken(TokenType.LEFT_PAREN);

            List<AstVarDeclaration> list = ParseArgumentDeclarations();
            
            ParseToken(TokenType.RIGHT_PAREN);
            return new AstFunctionArgumentsList(list);
        }

        private List<AstVarDeclaration> ParseArgumentDeclarations()
        {
            List<AstVarDeclaration> list = new List<AstVarDeclaration>();
            if (GetForwardToken().Type == TokenType.RIGHT_PAREN || HasErrors)
            {
                return list;
            }

            list.Add(ParseArgumentDecl()); // TODO: use DO-WHILE or recursion instead ?

            while (GetForwardToken().Type == TokenType.COMMA)
            {
                ParseToken(TokenType.COMMA);
                list.Add(ParseArgumentDecl());
            }

            return list;
        }

        private AstVarDeclaration ParseArgumentDecl()
        {
            AstId type = ParseId();

            if (GetForwardToken().Type == TokenType.LEFT_BRACKET)
            {
                ParseToken(TokenType.LEFT_BRACKET);
                ParseToken(TokenType.RIGHT_BRACKET);
                type = new AstArrayType(type);
            }

            AstId id = ParseId();
            return new AstVarDeclaration(type, id);
        }

        private AstStatements ParseStatements()
        {
            ParseToken(TokenType.LEFT_BRACE);

            List<AstStatement> list = new List<AstStatement>();

            TokenType type = GetForwardToken().Type;
            while (type != TokenType.RIGHT_BRACE && !HasErrors)
            {
                list.Add(ParseStatement());
                type = GetForwardToken().Type;
            }

            ParseToken(TokenType.RIGHT_BRACE);

            return new AstStatements(list);
        }

        private AstStatement ParseStatement()
        {
            switch (GetForwardToken().Type)
            {                
                case TokenType.IF:         
                    return ParseIfStatement();
                case TokenType.WHILE:      
                    return ParseWhileStatement();                
                case TokenType.ID:
                    return ParseAssigmentOrMethodCallStatement();
                case TokenType.LEFT_BRACE: 
                    return ParseStatements();
                default:
                    TriggerError("Expecting LEFT_BRACE, IF, WHILE or ID tokens.");
                    return new AstErroneousStatement();
            }
        }

        private AstWhileStatement ParseWhileStatement()
        {
            ParseToken(TokenType.WHILE);
            ParseToken(TokenType.LEFT_PAREN);
            AstExpression cond = ParseExpression();
            ParseToken(TokenType.RIGHT_PAREN);
            return new AstWhileStatement(cond, ParseStatements());
        }
        
        private AstIfStatement ParseIfStatement()
        {
            ParseToken(TokenType.IF);
            ParseToken(TokenType.LEFT_PAREN);
            AstExpression cond = ParseExpression();
            ParseToken(TokenType.RIGHT_PAREN);
            AstStatements thenStatements = ParseStatements();
            AstStatements elseOptStatements = null;
            if (GetForwardToken().Type == TokenType.ELSE)
            {
                ParseToken(TokenType.ELSE);
                elseOptStatements = ParseStatements();
            }

            return new AstIfStatement(cond, thenStatements, elseOptStatements);
        }

        private AstExpression ParseExpression()
        {
            // либо ID, либо IAstClassField, либо integer, 
            // либо выражения с +, -, *, или /

            if (GetForwardToken().Type == TokenType.LEFT_PAREN)
            {
                AstParenExpression expr = ParseParenExpression();
                if (NextTokenIsBinaryOperator())
                {
                    return CreateBinaryExpressionFromParts(GetNextToken(), expr, ParseExpression());
                }

                return expr;
            }

            return ParseIdOrBinaryExpression();
        }

        private AstParenExpression ParseParenExpression()
        {
            ParseToken(TokenType.LEFT_PAREN);
            AstExpression e = ParseExpression();
            ParseToken(TokenType.RIGHT_PAREN);
            return new AstParenExpression(e);
        }

        private bool NextTokenIsBinaryOperator()
        {
            switch (GetForwardToken().Type)
            {
                case TokenType.PLUS:
                case TokenType.MINUS:
                case TokenType.MULTIPLICATION:
                case TokenType.DIVISION:
                case TokenType.GT:
                case TokenType.LT:
                case TokenType.AND:
                case TokenType.OR:
                    return true;
                default:
                    return false;
            }
        }

        private AstExpression CreateBinaryExpressionFromParts(Token t, AstExpression left, AstExpression right)
        {
            switch (t.Type)
            {
                case TokenType.PLUS:     return new AstAddExpression(left, right);
                case TokenType.MINUS:    return new AstMinusExpression(left, right);
                case TokenType.MULTIPLICATION: return new AstMultiplicationExpression(left, right);
                case TokenType.DIVISION: return new AstDivisionExpression(left, right);
                case TokenType.GT:       return new AstLogicalExpression(TokenType.GT, left, right);
                case TokenType.LT:       return new AstLogicalExpression(TokenType.LT, left, right);
                case TokenType.AND:      return new AstLogicalExpression(TokenType.AND, left, right);
                case TokenType.OR:       return new AstLogicalExpression(TokenType.OR, left, right);
                default:                 return new AstErroneousExpression(t.ToString());
            }
        }

        private AstExpression ParseIdOrBinaryExpression()
        {
            AstSingleLiteralExpression e = ParseIdOrInteger();
            if (NextTokenIsBinaryOperator())
            {
                return CreateBinaryExpressionFromParts(GetNextToken(), e, ParseExpression());
            }
            return e;
        }

        private AstSingleLiteralExpression ParseIdOrInteger()
        {
            if (GetForwardToken().Type == TokenType.ID)
            {
                return ParseId();
            }

            return ParseInteger();
        }

        private AstInteger ParseInteger()
        {
            ParseToken(TokenType.INTEGER);
            return new AstInteger(currentToken.Attribute);
        }

        private AstStatement ParseAssigmentOrMethodCallStatement()
        {
            AstId id = ParseId();
            AstExpression leftArg = id;
            AstStatement result;            

            TokenType type = GetForwardToken().Type;
            if (type == TokenType.DOT) // id.id(args);
            {
                ParseToken(TokenType.DOT);
                leftArg = new AstDotExpression(id, ParseId());
                result = CreateFunctionCallNode(leftArg, ParseArgumentList());
            }
            else if (type == TokenType.LEFT_BRACKET) // id[exp] = exp;
            {
                ParseToken(TokenType.LEFT_BRACKET);
                leftArg = new AstArrayAccess(id, ParseExpression());
                ParseToken(TokenType.RIGHT_BRACKET);
                result = ParseAssigmentStatement(leftArg);
            }
            else // id = expression;
            {
                result = ParseAssigmentStatement(leftArg);
            }

            ParseToken(TokenType.SEMICOLON);
            return result;
        }

        private AstAssigmentStatement ParseAssigmentStatement(AstExpression left)
        {
            ParseToken(TokenType.ASSIGMENT);
            return new AstAssigmentStatement(left, ParseExpression());
        }

        private AstFunctionCall CreateFunctionCallNode(AstExpression obj, AstArgumentsList list)
        {   // TODO: move this logic to codedom
            if (obj is AstDotExpression && list.ExpressionList.Count == 1)
            {
                AstDotExpression dotExpr = obj as AstDotExpression;
                AstId leftOp = dotExpr.LeftOperand as AstId;
                AstId classField = dotExpr.ClassField as AstId;
                AstInteger intParam = list.ExpressionList[0] as AstInteger;

                if (leftOp != null && classField != null && intParam != null)
                {
                    if (leftOp.Value == "out" && classField.Value == "println")
                    {
                        return new AstConsoleWriteFunctionCall(dotExpr, intParam);
                    }
                }
            }

            return new AstFunctionCall(obj, list);            
        }

        private AstArgumentsList ParseArgumentList()
        {
            List<AstExpression> list = new List<AstExpression>();

            ParseToken(TokenType.LEFT_PAREN);
            if (GetForwardToken().Type != TokenType.RIGHT_PAREN)
            {
                list.Add(ParseExpression());
                while (GetForwardToken().Type == TokenType.COMMA)                
                {
                    ParseToken(TokenType.COMMA);
                    list.Add(ParseExpression());
                }
            }
            ParseToken(TokenType.RIGHT_PAREN);

            return new AstArgumentsList(list);
        }
    }
}
